﻿using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;
using FarseerPhysics.Common;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SpriteGame.Rendering;
using SpriteGame.UI;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision;
using System.Xml.Serialization;

#if XBOX
using Microsoft.Xna.Framework.Content;
#else
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
#endif

using Path = System.IO.Path;
using System.Xml;
using FarseerPhysics;
using SpriteGame.Helpers;
using FarseerPhysics.Dynamics.Contacts;


namespace SpriteGame
{
    public abstract class Scene:IFocusable
    {
        [XmlIgnore]
        protected Game1 parent;
        [XmlIgnore]
        public Game1 Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        UIPanel hud;
        internal UIPanel Hud
        {
            get { return hud; }
        }

        //Content Stuff
        [XmlIgnore]
        protected ContentManager content;

        bool isActive = false;
        public bool IsActive
        {
            get { return isActive; }
            set { isActive = value; }
        }

        //Physics Stuff
        [XmlIgnore]
        protected World world;

        [XmlIgnore]
        /// <summary>
        /// Gets the currently active physics world
        /// </summary>
        public World World
        {
            get { return world; }
        }
        [XmlIgnore]
        protected int stepCount = 0;

        //Graphics Stuff
        [XmlIgnore]
        ShapeRenderer spriteBatch;

        bool physicsPaused = false;
        public bool PausedPhysics
        {
            get { return physicsPaused; }
            set { physicsPaused = value; }
        }


        protected List<BaseSprite> entities;
        [XmlIgnore]
        List<BaseSprite> entitiesToRemove;
        [XmlIgnore]
        List<BaseSprite> entitiesToAdd;

        Particles particles;
        public Particles Particles
        {
            get { return particles; }
            set { particles = value; }
        }

        public Scene()
        {

        }
        
        public Scene(Game1 _parent)
        {
            parent = _parent;
        }

        public Scene(Game1 _parent, string _levelName)
        {
            parent = _parent;
            LoadScene(_levelName);
        }

        /// <summary>
        /// Loads the content to be used in the scene
        /// </summary>
        public virtual void LoadContent()
        {
            world = new World(new Vector2(0.0f, -50f));

            world.ContactManager.PreSolve += PreSolve;
            world.ContactManager.PostSolve += PostSolve;
            world.ContactManager.BeginContact += BeginContact;
            world.ContactManager.EndContact += EndContact;

            content = parent.Content;

            entities = new List<BaseSprite>();
            entitiesToRemove = new List<BaseSprite>();
            entitiesToAdd = new List<BaseSprite>();

            spriteBatch = new ShapeRenderer(parent.GraphicsDevice, parent);
            spriteBatch.LoadContent();

            BuildHUD();

            particles = new Particles(this);
        }

        public virtual void UnloadContent()
        {
            foreach (BaseSprite s in entities)
            {
                RemoveEntity(s);
            }
            //ensures that entities are not removed from the entlist until all updates are complete
            foreach (BaseSprite s in entitiesToRemove)
            {
                s.Dispose();
                entities.Remove(s);
            }
            entitiesToRemove.Clear();
            entities.Clear();
            entitiesToAdd.Clear();
        }

        /// <summary>
        /// Used to create the current UI instance
        /// </summary>
        public virtual void BuildHUD()
        {
            hud = new UIContainerPanel(0, 0, parent.GraphicsDevice.Viewport.Width, parent.GraphicsDevice.Viewport.Height);
        }

        /// <summary>
        /// Adds an entity to the current entity tree
        /// </summary>
        /// <param name="_e"></param>
        public void AddEntity(BaseSprite _e)
        {
            entitiesToAdd.Add(_e);
        }

        /// <summary>
        /// Adds entities to the current entity tree
        /// </summary>
        /// <param name="_e"></param>
        public void AddEntity(BaseSprite[] _e)
        {
            foreach (BaseSprite s in _e)
            {
                entitiesToAdd.Add(s);
            }
        }

        /// <summary>
        /// Removes the last entity to be added to the tree
        /// </summary>
        public void RemoveLastEntity()
        {
            if (entities.Count > 0)
            {
                entitiesToRemove.Add(entities[entities.Count - 1]);
            }
        }


        /// <summary>
        /// Removes an entity from the current tree DO NOT MODIFY LOUIS HACKS ONLY!
        /// </summary>
        /// <param name="_e"></param>
        public virtual void RemoveEntity(BaseSprite _e)
        {
            entitiesToRemove.Add(_e);
        }

        public void LoadScene(string _sceneName)
        {
            List<ObjectDef> objects = content.Load<List<ObjectDef>>("Levels/"+_sceneName);

            if (entities.Count > 0)
            {
                foreach (BaseSprite e in entities)
                {
                    RemoveEntity(e);
                }
            }

            foreach (ObjectDef d in objects)
            {
                Type t = Type.GetType(d.type);

                BaseSprite sprite = (BaseSprite)Activator.CreateInstance(t);
                sprite.StartPosition = d.position;
                sprite.StartRot = d.rotation;
                sprite.Texture = d.texture;
                sprite.BodyType = d.bodyType;

                AddEntity(sprite);
            }

            GC.Collect();
        }

        [XmlInclude(typeof(BodyType))]
        [XmlInclude(typeof(BaseSprite))]
        public void SaveScene(string _sceneName)
        {
            List<ObjectDef> objects = new List<ObjectDef>();

            foreach (BaseSprite e in entities)
            {
                if (e.CanSave)
                {
                    ObjectDef def = new ObjectDef();
                    def.type = e.GetType().ToString();
                    def.position = e.Position;
                    def.rotation = e.Rotation;
                    def.bodyType = e.BodyType;
                    def.texture = e.Texture;

                    objects.Add(def);
                }
            }

            if (objects.Count > 0)
            {
#if XBOX 
#else
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;

                using (XmlWriter writer = XmlWriter.Create("../../../Content/Levels/"+_sceneName+".xml", settings))
                {
                    IntermediateSerializer.Serialize(writer, objects, null);
                } 
#endif
            }

        }

        public virtual void Update(GameTime _time)
        {
            foreach (BaseSprite s in entitiesToAdd)
            {
                entities.Add(s);
                if (!s.IsLoaded)
                {
                    s.LoadContent(this);
                }
            }

            entitiesToAdd.Clear();

            //ensures that entities are not removed from the entlist until all updates are complete
            foreach (BaseSprite s in entitiesToRemove)
            {
                s.Dispose();
                entities.Remove(s);
                
            }
            entitiesToRemove.Clear();



            if (!physicsPaused)
            {
                float timeStep = Math.Min((float)_time.ElapsedGameTime.TotalMilliseconds * 0.001f, (1f / 30f));
                world.Step(timeStep);
                if (timeStep > 0.0f)
                {
                    ++stepCount;
                }
            }

            for (int i = 0; i < entities.Count; i++)
            {
                entities[i].Update(_time);
            }

            if (InputHandler.LeftClick())
            {
                if (hud.Bounds.Contains(InputHandler.GetMousePoint()))
                    hud.GetCollider(InputHandler.GetMousePoint());
            }

            if (InputHandler.IsGamepadButtonPressed(Buttons.Start))
            {
                parent.ChangeScene(SceneIndex.MENU, "");
            }

            particles.Update(_time);

            hud.Update(Vector2.Zero);
        }

        public virtual void DrawHUD(SpriteBatch _batch)
        {
            if (hud.IsVisible)
            {
                hud.Render(_batch, Vector2.Zero);
            }
        }

        List<BaseSprite> spritesToDraw = new List<BaseSprite>();

        public virtual void Draw(GameTime _time)
        {
            // Make a small box.
            AABB aabb;
            Vector2 d = new Vector2(0.1f, 0.1f);
            aabb.LowerBound = parent.ConvertScreenToWorld(0,0) - d;
            aabb.UpperBound = parent.ConvertScreenToWorld(parent.GraphicsDevice.Viewport.Width,
                parent.GraphicsDevice.Viewport.Height) + d;

            //FarseerPhysics.DebugViewXNA.DebugViewXNA.Instance.DrawAABB(ref aabb, Color.Red);

            // Query the world for overlapping shapes.
            
            world.QueryAABB(
                fixture =>
                {
                    Body body = fixture.Fixture.Body;

                    spritesToDraw.Add((BaseSprite)body.UserData);
                    return true;
                }, ref aabb);

            parent.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            parent.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            parent.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
            spriteBatch.BeginBatch();

            for (int i = 0; i < entities.Count; i++)
            {
                entities[i].Draw(spriteBatch);
            }
            //foreach (BaseSprite s in entities)
            //{
            //    s.Draw(spriteBatch);
            //}

            spritesToDraw.Clear();

            particles.Draw(spriteBatch);

            spriteBatch.EndBatch();
        }

        #region IFocusable Members

        public bool HasFocus
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void UpdateInput(GameTime _time)
        {
            throw new NotImplementedException();
        }

        protected virtual void BeginContact(Contact contact)
        {
        }

        protected virtual void EndContact(Contact contact)
        {
        }

        protected virtual void PreSolve(Contact contact, ref Manifold oldManifold)
        {
        }

        protected virtual void PostSolve(Contact contact, ref ContactImpulse impulse)
        {
        }

        #endregion
    }
}
